#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>
#include "sconf.h"
#include "log.h"
#include "securec.h"

#define COMMENT_CH   '#'

static char* trim(char* line)
{
    while (isspace(*line)) {
        *line++ = 0;
    }

    int len = (int)strlen(line);
    if (len <= 0) {
        return NULL;
    }

    while (isspace(line[len - 1])) {
        line[len - 1] = 0;
        len--;
    }

    //LOG_DBG("after trim:line=[%s] len=%d\n", line, len);

    if (len == 0) {
        return NULL;
    }

    return line;
}
static inline sconf_pat_stru* find_pat(char* key, const sconf_pat_stru* table, int cnt)
{
    for (int i = 0; i < cnt; i++) {
        if (!strcmp(table[i].key, key)) {
            return (sconf_pat_stru*)&table[i];
        }
    }

    return NULL;
}
static inline int str2mac(char* mac, int size, char* str)
{
    if (size < 6) { // 6 for mac len
        LOG_ERR("size too small for mac, size=%d\n", size);
        return -1;
    }

    unsigned tmp[7];

    // note: Added %c at the end of the format string to reject excess characters in the input
    if (6 == sscanf_s((const char*)str, "%x:%x:%x:%x:%x:%x",
        &tmp[0], &tmp[1], &tmp[2],
        &tmp[3], &tmp[4], &tmp[5])) {
        for (int i = 0; i < 6; i++) {
            mac[i] = (char)tmp[i];
        }
        return  0;
    }

    return -1;
}
static int sconf_switch(sconf_pat_stru* pat, int ln, char* val)
{
    switch (pat->type) {
    case SCONF_TYPE_INT:
        *(int*)pat->addr = (int)strtol(val, NULL, 0);
        break;

    case SCONF_TYPE_MAC:
        if (str2mac((char*)pat->addr, pat->size, val)) {
            LOG_ERR("line=%d:illegal mac addr\n", ln);
            return -1;
        }
        break;

    case SCONF_TYPE_STR:
        if ((int)strlen(val) + 1 > pat->size) {
            LOG_ERR("line=%d:string too long, pat size=%d\n", ln, pat->size);  
            return -1;
        }
        int ret = strcpy_s((char*)pat->addr, pat->size, val);
        if (ret != EOK) {
            printf("sconf_switch strcpy_s fail\n");
            return -1;
        }
        break;

    default:
        LOG_ERR("invalid type=%d\n", pat->type);
        return -1;
    }

    return 0;
}
static int sconf_process_line(char* line, const sconf_pat_stru* table, int cnt, int ln)
{
    char* comment = strchr(line, COMMENT_CH);
    if (comment) {
        *comment = 0;
        LOG_DBG("line=%d:trim comment\n", ln);
    }

    line = trim(line);
    if (line == NULL) {
        LOG_DBG("line=%d:blank line\n", ln);
        return 0;
    }

    char* mid = strchr(line, '=');
    if (mid == NULL) {
        LOG_ERR("line=%d:invalid line, no = found\n", ln);
        return -1;
    }

    *mid = 0;
    char* key = trim(line);
    char* val = trim(mid + 1);
    if (key == NULL || val == NULL) {
        LOG_ERR("line=%d:invalid line, key or value not found\n", ln);
        return -1;
    }
    LOG_DBG("line=%d:key=[%s] val=[%s]\n", ln, key, val);
    sconf_pat_stru* pat = find_pat(key, table, cnt);
    if (pat == NULL) {
        LOG_ERR("line=%d:unknown key=[%s]\n", ln, key);   
        return -1;
    }

    return sconf_switch(pat, ln, val);
}
int sconf_parse(const char* conf, const sconf_pat_stru* table, int cnt)
{
    if (conf == NULL || table == NULL || cnt <= 0) {
        return -1;
    }

    FILE* fp = fopen(conf, "r");
    if (fp == NULL) {
        LOG_ERR("open [%s] failed, error=%s\n", conf, strerror(errno));
        return -1;
    }

    char* buf = malloc(SCONF_MAX_STR_LEN);
    if (buf == NULL) {
        LOG_ERR("maloc faild, size=%d error=%s\n", SCONF_MAX_STR_LEN, strerror(errno));
        fclose(fp);
        return -1;
    }

    int ret = 0;
    int ln = 1;
    while (fgets(buf, SCONF_MAX_STR_LEN, fp) != NULL) {
        if (sconf_process_line(buf, table, cnt, ln++)) {
            ret = -1;
            break;
        }
    }

    free(buf);
    fclose(fp);

    return ret;
}
